package transformnumber

import (
	"errors"
	"fmt"
	"log"
	"math"
	"regexp"
	"strconv"
	"strings"
)

type Cn2An struct {
	allNum          string
	allUnit         string
	strictCnNumber  map[string]string
	normalCnNumber  map[string]string
	checkKeyDict    map[string]string
	patternDict     map[string]map[string]*regexp.Regexp
	ac              *An2Cn
	modeList        []string
	yjfPattern      *regexp.Regexp
	pattern1        *regexp.Regexp
	ptnAllNum       *regexp.Regexp
	ptnSpeakingMode *regexp.Regexp
}

func NewCn2An() *Cn2An {
	c := &Cn2An{
		allNum:         strings.Join(getKeys(NUMBER_CN2AN), ""),
		allUnit:        strings.Join(getKeys(UNIT_CN2AN), ""),
		strictCnNumber: STRICT_CN_NUMBER,
		normalCnNumber: NORMAL_CN_NUMBER,
		ac:             NewAn2Cn(),
		modeList:       []string{"strict", "normal", "smart"},
	}

	c.checkKeyDict = map[string]string{
		"strict": strings.Join(getValues(c.strictCnNumber), "") + "点负",
		"normal": strings.Join(getValues(c.normalCnNumber), "") + "点负",
		"smart":  strings.Join(getValues(c.normalCnNumber), "") + "点负" + "01234567890.-",
	}

	c.patternDict = c.getPattern()
	c.yjfPattern = regexp.MustCompile(fmt.Sprintf("^.*?[元圆][%s]角([%s]分)?$", c.allNum, c.allNum))
	c.pattern1 = regexp.MustCompile(fmt.Sprintf("^-?\\d+(\\.\\d+)?[%s]?$", c.allUnit))
	c.ptnAllNum = regexp.MustCompile(fmt.Sprintf("^[%s]+$", c.allNum))
	c.ptnSpeakingMode = regexp.MustCompile(fmt.Sprintf("^([%s]{0,2}[%s])+[%s]$", c.allNum, c.allUnit, c.allNum))

	return c
}

/*
	中文数字转阿拉伯数字

:param inputs: 中文数字、阿拉伯数字、中文数字和阿拉伯数字
:param mode: strict 严格，normal 正常，smart 智能
:return: 阿拉伯数字
*/
func (c *Cn2An) Cn2An(inputStr string, mode string) (float64, error) {

	if inputStr == "" {
		return 0, errors.New("输入数据为空")
	}

	if !contains(c.modeList, mode) {
		return 0, fmt.Errorf("mode 仅支持 %v", c.modeList)
	}

	// todo: 繁体转简体, 全角转半角

	// Data preprocessing
	inputStr = strings.ReplaceAll(inputStr, "廿", "二十")
	inputStr = strings.ReplaceAll(inputStr, "卅", "三十")

	sign, integerData, decimalData, isAllNum, err := c.checkInputDataIsValid(inputStr, mode)
	if err != nil {
		return 0, err
	}

	// smart 模式下的特殊情况
	if sign == 0 {
		intPart, _ := c.integerConvert(integerData)
		return intPart, nil
	}

	var output float64
	if !isAllNum {
		if decimalData == "" {
			output, err = c.integerConvert(integerData)
		} else {
			intPart, err := c.integerConvert(integerData)
			if err != nil {
				return 0, err
			}
			decPart, err := c.decimalConvert(decimalData)
			if err != nil {
				return 0, err
			}
			output = intPart + decPart

			// fix 1 + 0.57 = 1.5699999999999998
			output = math.Round(output*math.Pow10(len([]rune(decimalData)))) / math.Pow10(len([]rune(decimalData)))
		}
	} else {
		if decimalData == "" {
			output, err = c.directConvert(integerData)
		} else {
			intPart, err := c.directConvert(integerData)
			if err != nil {
				return 0, err
			}
			decPart, err := c.decimalConvert(decimalData)
			if err != nil {
				return 0, err
			}
			output = float64(intPart) + decPart

			// fix 1 + 0.57 = 1.5699999999999998
			output = math.Round(output*math.Pow10(len([]rune(decimalData)))) / math.Pow10(len([]rune(decimalData)))
		}
	}

	if err != nil {
		return 0, err
	}

	return float64(sign) * output, nil
}

func (c *Cn2An) getPattern() map[string]map[string]*regexp.Regexp {
	// 整数严格检查
	_0 := "[零]"
	_1_9 := "[一二三四五六七八九]"
	_10_99 := fmt.Sprintf("%s?[十]%s?", _1_9, _1_9)
	_1_99 := fmt.Sprintf("(%s|%s)", _10_99, _1_9)
	_100_999 := fmt.Sprintf("(%s[百]([零]%s)?|%s[百]%s)", _1_9, _1_9, _1_9, _10_99)
	_1_999 := fmt.Sprintf("(%s|%s)", _100_999, _1_99)
	_1000_9999 := fmt.Sprintf("(%s[千]([零]%s)?|%s[千]%s)", _1_9, _1_99, _1_9, _100_999)
	_1_9999 := fmt.Sprintf("(%s|%s)", _1000_9999, _1_999)
	_10000_99999999 := fmt.Sprintf("(%s[万]([零]%s)?|%s[万]%s)", _1_9999, _1_999, _1_9999, _1000_9999)
	_1_99999999 := fmt.Sprintf("(%s|%s)", _10000_99999999, _1_9999)
	_100000000_9999999999999999 := fmt.Sprintf("(%s[亿]([零]%s)?|%s[亿]%s)", _1_99999999, _1_99999999, _1_99999999, _10000_99999999)
	_1_9999999999999999 := fmt.Sprintf("(%s|%s)", _100000000_9999999999999999, _1_99999999)
	strIntPattern := fmt.Sprintf("^(%s|%s)$", _0, _1_9999999999999999)
	norIntPattern := fmt.Sprintf("^(%s|%s)$", _0, _1_9999999999999999)

	strDecPattern := "^[零一二三四五六七八九]{0,15}[一二三四五六七八九]$"
	norDecPattern := "^[零一二三四五六七八九]{0,16}$"

	for strNum, strRepl := range c.strictCnNumber {
		strIntPattern = strings.ReplaceAll(strIntPattern, strNum, strRepl)
		strDecPattern = strings.ReplaceAll(strDecPattern, strNum, strRepl)
	}
	for norNum, norRepl := range c.normalCnNumber {
		norIntPattern = strings.ReplaceAll(norIntPattern, norNum, norRepl)
		norDecPattern = strings.ReplaceAll(norDecPattern, norNum, norRepl)
	}

	patternDict := map[string]map[string]*regexp.Regexp{
		"strict": {
			"int": regexp.MustCompile(strIntPattern),
			"dec": regexp.MustCompile(strDecPattern),
		},
		"normal": {
			"int": regexp.MustCompile(norIntPattern),
			"dec": regexp.MustCompile(norDecPattern),
		},
	}

	return patternDict
}

/*
return sign, integerData, decimalData, isAllNum, err
sign: 正负号
integerData: 整数部分
decimalData: 小数部分
isAllNum: 是否全为数字
err: 错误信息
*/
func (c *Cn2An) checkInputDataIsValid(checkData string, mode string) (int, string, string, bool, error) {
	// 去除 元整、圆整、元正、圆正
	stopWords := []string{"元整", "圆整", "元正", "圆正"}

	for _, word := range stopWords {
		checkData = strings.TrimSuffix(checkData, word)
	}

	// 去除 元、圆
	if mode != "strict" {
		normalStopWords := []string{"圆", "元"}
		for _, word := range normalStopWords {
			checkData = strings.TrimSuffix(checkData, word)
		}
	}

	// 处理元角分
	if c.yjfPattern.MatchString(checkData) {
		checkData = strings.ReplaceAll(checkData, "元", "点")
		checkData = strings.ReplaceAll(checkData, "角", "")
		checkData = strings.ReplaceAll(checkData, "分", "")
	}

	// 处理特殊问法：一千零十一 一万零百一十一
	checkData = strings.ReplaceAll(checkData, "零十", "零一十")
	checkData = strings.ReplaceAll(checkData, "零百", "零一百")

	for _, data := range checkData {
		if !strings.ContainsRune(c.checkKeyDict[mode], data) {
			return 0, "", "", false, errors.New("当前为" + mode + "模式，输入的数据不在转化范围内：" + string(data))
		}
	}

	// 确定正负号
	dataRune := []rune(checkData)
	sign := 1
	if dataRune[0] == '负' {
		checkData = string(dataRune[1:])
		sign = -1
	}

	var integerData, decimalData string
	var isAllNum bool

	if strings.Contains(checkData, "点") {
		splitData := strings.Split(checkData, "点")
		if len(splitData) == 2 {
			integerData, decimalData = splitData[0], splitData[1]
			if mode == "smart" {
				integerData = regexp.MustCompile(`\d+`).ReplaceAllStringFunc(integerData, func(s string) string {
					data, err := c.ac.An2Cn(s, mode)
					if err != nil {
						log.Printf("An2Cn 转换失败：%v, 数据：%s", err, s)
						return ""
					}
					return data
				})
				decimalData = regexp.MustCompile(`\d+`).ReplaceAllStringFunc(decimalData, func(s string) string {
					return c.copyNum(s)
				})
				mode = "normal"
			}
		} else {
			return 0, "", "", false, errors.New("数据中包含不止一个点！")
		}
	} else {
		integerData = checkData
		// 将 smart 模式中的阿拉伯数字转化成中文数字
		if mode == "smart" {
			// 10.1万  10.1
			if c.pattern1.MatchString(integerData) {
				if integerData == c.pattern1.FindString(integerData) {
					runeIntegerData := []rune(integerData)
					lastChar := runeIntegerData[len(runeIntegerData)-1]
					if _, ok := UNIT_CN2AN_RUNE[lastChar]; ok {
						output, _ := strconv.ParseFloat(string(runeIntegerData[:len(runeIntegerData)-1]), 64)
						output *= float64(UNIT_CN2AN_RUNE[lastChar])
						return 0, strconv.FormatFloat(output, 'f', -1, 64), "", false, nil
					} else {
						output, _ := strconv.ParseFloat(integerData, 64)
						return 0, strconv.FormatFloat(output, 'f', -1, 64), "", false, nil
					}
				}
			}
			integerData = regexp.MustCompile(`\d+`).ReplaceAllStringFunc(integerData, func(s string) string {
				data, err := c.ac.An2Cn(s, mode)
				if err != nil {
					log.Printf("An2Cn 转换失败：%v, 数据：%s", err, s)
					return ""
				}
				return data
			})
			mode = "normal"
		}
	}

	resultInt := c.patternDict[mode]["int"].FindString(integerData)
	if resultInt != "" {
		if resultInt == integerData {
			if decimalData != "" {
				resultDec := c.patternDict[mode]["dec"].FindString(decimalData)
				if resultDec != "" && resultDec == decimalData {
					return sign, integerData, decimalData, false, nil
				}
			} else {
				return sign, integerData, decimalData, false, nil
			}
		}
	} else {
		if mode == "strict" {
			return sign, "", "", false, fmt.Errorf("不符合格式的数据：%s", integerData)
		} else if mode == "normal" {
			// 纯数模式：一二三
			resultAllNum := c.ptnAllNum.FindString(integerData)
			if resultAllNum != "" && resultAllNum == integerData {
				if decimalData != "" {
					resultDec := c.patternDict[mode]["dec"].FindString(decimalData)
					if resultDec != "" && resultDec == decimalData {
						return sign, integerData, decimalData, true, nil
					}
				} else {
					return sign, integerData, decimalData, true, nil
				}
			}

			// 口语模式：一万二，两千三，三百四，十三万六，一百二十五万三
			resultSpeakingMode := c.ptnSpeakingMode.FindStringSubmatch(integerData)
			lenSpeakingMode := len(resultSpeakingMode)
			if len([]rune(integerData)) >= 3 && lenSpeakingMode > 0 && resultSpeakingMode[0] == integerData {
				r := []rune(resultSpeakingMode[lenSpeakingMode-1])
				lastUnit := string(r[len(r)-1])
				unit := UNIT_LOW_AN2CN[UNIT_CN2AN[lastUnit]/10]
				integerData = integerData + unit
				if decimalData != "" {
					resultDec := c.patternDict[mode]["dec"].FindString(decimalData)
					if resultDec != "" && resultDec == decimalData {
						return sign, integerData, decimalData, false, nil
					}
				} else {
					return sign, integerData, decimalData, false, nil
				}
			}
		}
	}

	return sign, integerData, decimalData, isAllNum, errors.New("不符合格式的数据：" + checkData)
}

func (c *Cn2An) integerConvert(integerData string) (float64, error) {
	var outputInteger float64
	unit := 1.0
	tenThousandUnit := 1.0

	integerDataRune := []rune(integerData)
	for i := len(integerDataRune) - 1; i >= 0; i-- {
		cnNum := string(integerDataRune[i])
		if num, ok := NUMBER_CN2AN[cnNum]; ok {
			outputInteger += float64(num) * unit
		} else if unitValue, ok := UNIT_CN2AN[cnNum]; ok {
			unit = float64(unitValue)
			if unitValue%10000 == 0 {
				if unit > tenThousandUnit {
					tenThousandUnit = unit
				} else {
					tenThousandUnit = unit * tenThousandUnit
					unit = tenThousandUnit
				}
			}
			if unit < tenThousandUnit {
				unit = unit * tenThousandUnit
			}
			if i == 0 {
				outputInteger += unit
			}
		} else {
			return 0, fmt.Errorf("%s 不在转化范围内", cnNum)
		}
	}

	return outputInteger, nil
}

func (c *Cn2An) decimalConvert(decimalData string) (float64, error) {
	decimalDataRune := []rune(decimalData)
	lenDecimalData := len(decimalDataRune)
	if lenDecimalData > 16 {
		fmt.Printf("注意：小数部分长度为 %d ，将自动截取前 16 位有效精度！\n", lenDecimalData)
		// decimalData = string(decimalDataRune[:16])
		decimalDataRune = decimalDataRune[:16]
		lenDecimalData = 16
	}

	var outputDecimal float64
	for i := lenDecimalData - 1; i >= 0; i-- {
		unitKey := NUMBER_CN2AN[string(decimalDataRune[i])]
		outputDecimal += float64(unitKey) * math.Pow10(-(i + 1))
	}

	outputDecimal = math.Round(outputDecimal*math.Pow10(lenDecimalData)) / math.Pow10(lenDecimalData)

	return outputDecimal, nil
}

func (c *Cn2An) directConvert(data string) (float64, error) {
	var outputData float64
	dataRune := []rune(data)
	for i := len(dataRune) - 1; i >= 0; i-- {
		unitKey := NUMBER_CN2AN[string(dataRune[i])]
		outputData += float64(unitKey) * math.Pow10(len(dataRune)-i-1)
	}
	return outputData, nil
}

// Helper functions

func getKeys(m map[string]int) []string {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}

func getValues(m map[string]string) []string {
	values := make([]string, 0, len(m))
	for _, v := range m {
		values = append(values, v)
	}
	return values
}

func contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}

func (c *Cn2An) copyNum(num string) string {
	var cnNum strings.Builder
	for _, n := range num {
		digit, err := strconv.Atoi(string(n))
		if err != nil {
			// Handle the error if the character is not a valid digit
			log.Printf("copyNum 转换失败：%v, 数据：%s, 字符：%v", err, num, n)
			continue
		}
		cnNum.WriteString(NUMBER_LOW_AN2CN[digit])
	}
	return cnNum.String()
}
